/*
 * %W% %E%
 *
 * Copyright (c) 2006, Oracle and/or its affiliates. All rights reserved.
 * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 */

package java.awt.geom;

import java.util.*;

/**
 * The <code>FlatteningPathIterator</code> class returns a flattened view of
 * another {@link PathIterator} object.  Other {@link java.awt.Shape Shape} 
 * classes can use this class to provide flattening behavior for their paths
 * without having to perform the interpolation calculations themselves.
 *
 * @version 1.6 06/29/98
 * @author Jim Graham
 */
public class FlatteningPathIterator implements PathIterator {
    static final int GROW_SIZE = 24;	// Multiple of cubic & quad curve size

    PathIterator src;			// The source iterator

    double squareflat;			// Square of the flatness parameter
					// for testing against squared lengths

    int limit;				// Maximum number of recursion levels

    double hold[] = new double[14];	// The cache of interpolated coords
					// Note that this must be long enough
					// to store a full cubic segment and
					// a relative cubic segment to avoid
					// aliasing when copying the coords
					// of a curve to the end of the array.
					// This is also serendipitously equal
					// to the size of a full quad segment
					// and 2 relative quad segments.

    double curx, cury;			// The ending x,y of the last segment

    double movx, movy;			// The x,y of the last move segment

    int holdType;			// The type of the curve being held
					// for interpolation

    int holdEnd;			// The index of the last curve segment
					// being held for interpolation

    int holdIndex;			// The index of the curve segment
					// that was last interpolated.  This
					// is the curve segment ready to be
					// returned in the next call to
					// currentSegment().

    int levels[];			// The recursion level at which
					// each curve being held in storage
					// was generated.

    int levelIndex;			// The index of the entry in the
					// levels array of the curve segment
					// at the holdIndex

    boolean done;			// True when iteration is done

    /**
     * Constructs a new <code>FlatteningPathIterator</code> object that 
     * flattens a path as it iterates over it.  The iterator does not 
     * subdivide any curve read from the source iterator to more than 
     * 10 levels of subdivision which yields a maximum of 1024 line 
     * segments per curve.
     * @param src the original unflattened path being iterated over
     * @param flatness the maximum allowable distance between the
     * control points and the flattened curve
     */
    public FlatteningPathIterator(PathIterator src, double flatness) {
	this(src, flatness, 10);
    }

    /**
     * Constructs a new <code>FlatteningPathIterator</code> object 
     * that flattens a path as it iterates over it.
     * The <code>limit</code> parameter allows you to control the
     * maximum number of recursive subdivisions that the iterator
     * can make before it assumes that the curve is flat enough
     * without measuring against the <code>flatness</code> parameter.
     * The flattened iteration therefore never generates more than
     * a maximum of <code>(2^limit)</code> line segments per curve.
     * @param src the original unflattened path being iterated over
     * @param flatness the maximum allowable distance between the
     * control points and the flattened curve
     * @param limit the maximum number of recursive subdivisions
     * allowed for any curved segment
     * @exception <code>IllegalArgumentException</code> if 
     * 		<code>flatness</code> or <code>limit</code>
     *		is less than zero
     */
    public FlatteningPathIterator(PathIterator src, double flatness,
				  int limit) {
	if (flatness < 0.0) {
	    throw new IllegalArgumentException("flatness must be >= 0");
	}
	if (limit < 0) {
	    throw new IllegalArgumentException("limit must be >= 0");
	}
	this.src = src;
	this.squareflat = flatness * flatness;
	this.limit = limit;
	this.levels = new int[limit + 1];
	// prime the first path segment
	next(false);
    }

    /**
     * Returns the flatness of this iterator.
     * @return the flatness of this <code>FlatteningPathIterator</code>.
     */
    public double getFlatness() {
	return Math.sqrt(squareflat);
    }

    /**
     * Returns the recursion limit of this iterator.
     * @return the recursion limit of this 
     * <code>FlatteningPathIterator</code>.
     */
    public int getRecursionLimit() {
	return limit;
    }

    /**
     * Returns the winding rule for determining the interior of the
     * path.
     * @return the winding rule of the original unflattened path being
     * iterated over.
     * @see PathIterator#WIND_EVEN_ODD
     * @see PathIterator#WIND_NON_ZERO
     */
    public int getWindingRule() {
	return src.getWindingRule();
    }

    /**
     * Tests if the iteration is complete.
     * @return <code>true</code> if all the segments have
     * been read; <code>false</code> otherwise.
     */
    public boolean isDone() {
	return done;
    }

    /*
     * Ensures that the hold array can hold up to (want) more values.
     * It is currently holding (hold.length - holdIndex) values.
     */
    void ensureHoldCapacity(int want) {
	if (holdIndex - want < 0) {
	    int have = hold.length - holdIndex;
	    int newsize = hold.length + GROW_SIZE;
	    double newhold[] = new double[newsize];
	    System.arraycopy(hold, holdIndex,
			     newhold, holdIndex + GROW_SIZE,
			     have);
	    hold = newhold;
	    holdIndex += GROW_SIZE;
	    holdEnd += GROW_SIZE;
	}
    }

    /**
     * Moves the iterator to the next segment of the path forwards
     * along the primary direction of traversal as long as there are
     * more points in that direction.
     */
    public void next() {
	next(true);
    }

    private void next(boolean doNext) {
	int level;

	if (holdIndex >= holdEnd) {
	    if (doNext) {
		src.next();
	    }
	    if (src.isDone()) {
		done = true;
		return;
	    }
	    holdType = src.currentSegment(hold);
	    levelIndex = 0;
	    levels[0] = 0;
	}

	switch (holdType) {
	case SEG_MOVETO:
	case SEG_LINETO:
	    curx = hold[0];
	    cury = hold[1];
	    if (holdType == SEG_MOVETO) {
		movx = curx;
		movy = cury;
	    }
	    holdIndex = 0;
	    holdEnd = 0;
	    break;
	case SEG_CLOSE:
	    curx = movx;
	    cury = movy;
	    holdIndex = 0;
	    holdEnd = 0;
	    break;
	case SEG_QUADTO:
	    if (holdIndex >= holdEnd) {
		// Move the coordinates to the end of the array.
		holdIndex = hold.length - 6;
		holdEnd = hold.length - 2;
		hold[holdIndex + 0] = curx;
		hold[holdIndex + 1] = cury;
		hold[holdIndex + 2] = hold[0];
		hold[holdIndex + 3] = hold[1];
		hold[holdIndex + 4] = curx = hold[2];
		hold[holdIndex + 5] = cury = hold[3];
	    }

	    level = levels[levelIndex];
	    while (level < limit) {
		if (QuadCurve2D.getFlatnessSq(hold, holdIndex) < squareflat) {
		    break;
		}

		ensureHoldCapacity(4);
		QuadCurve2D.subdivide(hold, holdIndex,
				      hold, holdIndex - 4,
				      hold, holdIndex);
		holdIndex -= 4;

		// Now that we have subdivided, we have constructed
		// two curves of one depth lower than the original
		// curve.  One of those curves is in the place of
		// the former curve and one of them is in the next
		// set of held coordinate slots.  We now set both
		// curves level values to the next higher level.
		level++;
		levels[levelIndex] = level;
		levelIndex++;
		levels[levelIndex] = level;
	    }

	    // This curve segment is flat enough, or it is too deep
	    // in recursion levels to try to flatten any more.  The
	    // two coordinates at holdIndex+4 and holdIndex+5 now
	    // contain the endpoint of the curve which can be the
	    // endpoint of an approximating line segment.
	    holdIndex += 4;
	    levelIndex--;
	    break;
	case SEG_CUBICTO:
	    if (holdIndex >= holdEnd) {
		// Move the coordinates to the end of the array.
		holdIndex = hold.length - 8;
		holdEnd = hold.length - 2;
		hold[holdIndex + 0] = curx;
		hold[holdIndex + 1] = cury;
		hold[holdIndex + 2] = hold[0];
		hold[holdIndex + 3] = hold[1];
		hold[holdIndex + 4] = hold[2];
		hold[holdIndex + 5] = hold[3];
		hold[holdIndex + 6] = curx = hold[4];
		hold[holdIndex + 7] = cury = hold[5];
	    }

	    level = levels[levelIndex];
	    while (level < limit) {
		if (CubicCurve2D.getFlatnessSq(hold, holdIndex) < squareflat) {
		    break;
		}

		ensureHoldCapacity(6);
		CubicCurve2D.subdivide(hold, holdIndex,
				       hold, holdIndex - 6,
				       hold, holdIndex);
		holdIndex -= 6;

		// Now that we have subdivided, we have constructed
		// two curves of one depth lower than the original
		// curve.  One of those curves is in the place of
		// the former curve and one of them is in the next
		// set of held coordinate slots.  We now set both
		// curves level values to the next higher level.
		level++;
		levels[levelIndex] = level;
		levelIndex++;
		levels[levelIndex] = level;
	    }

	    // This curve segment is flat enough, or it is too deep
	    // in recursion levels to try to flatten any more.  The
	    // two coordinates at holdIndex+6 and holdIndex+7 now
	    // contain the endpoint of the curve which can be the
	    // endpoint of an approximating line segment.
	    holdIndex += 6;
	    levelIndex--;
	    break;
	}
    }

    /**
     * Returns the coordinates and type of the current path segment in
     * the iteration.
     * The return value is the path segment type:
     * SEG_MOVETO, SEG_LINETO, or SEG_CLOSE.
     * A float array of length 6 must be passed in and can be used to
     * store the coordinates of the point(s).
     * Each point is stored as a pair of float x,y coordinates.
     * SEG_MOVETO and SEG_LINETO types return one point,
     * and SEG_CLOSE does not return any points.
     * @param coords an array that holds the data returned from
     * this method
     * @return the path segment type of the current path segment.
     * @exception <code>NoSuchElementException</code> if there
     *		are no more elements in the flattening path to be
     *		returned.
     * @see PathIterator#SEG_MOVETO
     * @see PathIterator#SEG_LINETO
     * @see PathIterator#SEG_CLOSE
     */
    public int currentSegment(float[] coords) {
	if (isDone()) {
	    throw new NoSuchElementException("flattening iterator out of bounds");
	}
	int type = holdType;
	if (type != SEG_CLOSE) {
	    coords[0] = (float) hold[holdIndex + 0];
	    coords[1] = (float) hold[holdIndex + 1];
	    if (type != SEG_MOVETO) {
		type = SEG_LINETO;
	    }
	}
	return type;
    }

    /**
     * Returns the coordinates and type of the current path segment in
     * the iteration.
     * The return value is the path segment type:
     * SEG_MOVETO, SEG_LINETO, or SEG_CLOSE.
     * A double array of length 6 must be passed in and can be used to
     * store the coordinates of the point(s).
     * Each point is stored as a pair of double x,y coordinates.
     * SEG_MOVETO and SEG_LINETO types return one point,
     * and SEG_CLOSE does not return any points.
     * @param coords an array that holds the data returned from
     * this method
     * @return the path segment type of the current path segment.
     * @exception <code>NoSuchElementException</code> if there
     *          are no more elements in the flattening path to be
     *          returned.
     * @see PathIterator#SEG_MOVETO
     * @see PathIterator#SEG_LINETO
     * @see PathIterator#SEG_CLOSE
     */
    public int currentSegment(double[] coords) {
	if (isDone()) {
	    throw new NoSuchElementException("flattening iterator out of bounds");
	}
	int type = holdType;
	if (type != SEG_CLOSE) {
	    coords[0] = hold[holdIndex + 0];
	    coords[1] = hold[holdIndex + 1];
	    if (type != SEG_MOVETO) {
		type = SEG_LINETO;
	    }
	}
	return type;
    }
}
